﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Xml;

namespace Codemonk.Updater.Client.Common
{
    public static class Updater
    {
        static Updater()
        {
            basedir = Path.GetDirectoryName(typeof(Updater).Assembly.CodeBase.Substring("file:///".Length));
            var dir = new DirectoryInfo(basedir);
            Func<FileInfo[], bool> isNullOrEmpty = infos => infos == null || infos.Length == 0;
            var configFiles = dir.GetFiles("*updateconfig.xml", SearchOption.AllDirectories);
            if (isNullOrEmpty(configFiles)) return;
            var document = new XmlDocument();
            document.Load(configFiles[0].FullName);
            server = document.SelectSingleNode("/configuration/codemonk.updater.updateinfo/server").InnerText;
            appid = document.SelectSingleNode("/configuration/codemonk.updater.updateinfo/appid").InnerText;
            client = UpdateClientManager.GetClient(server);
        }


        private static string server, appid, basedir;
        private static IUpdateClient client;

        public static void Check(Action<Exception> error, Action<Inventory> back)
        {
            if (back == null) return;
            InnerDownload(string.Empty, error, stream => back(ReadInventory.Read(stream)));
        }

        private static void InnerDownload(string fileName, Action<Exception> error, Action<MemoryStream> back)
        {
            if (back == null) return;
            try
            {
                if (client == null) { back(null); return; }
                client.SendUpdateInstruction(fileName, appid, error, back);
            }
            catch (Exception e)
            {
                if (error != null) error(e);
            }
        }

        private static void DownloadFile(string fileName, Action<Exception> error, Action back)
        {
            if (back == null) return;
            InnerDownload(fileName, error, stream =>
            {
                try
                {
                    var fullpath = Path.Combine(basedir, fileName);
                    try { }
                    finally
                    {
                        var dirname = Path.GetDirectoryName(fullpath);
                        if (!Directory.Exists(dirname)) Directory.CreateDirectory(dirname);
                    }
                    using (var file = File.Create(fullpath))
                    {
                        stream.WriteTo(file);
                        stream.Flush();
                    }
                    back();
                }
                catch (Exception e)
                {
                    if (error != null) error(e);
                }
            });
        }

        public static void Update(Inventory inventory, Action<Exception> error, Action<string> start, Action<bool, string> end, Action back)
        {
            if (back == null) return;
            var collection = inventory.Items;
            List<InventoryFileItem> files = new List<InventoryFileItem>();
            foreach (var item in collection)
            {
                var fileName = item.Name;
                var fullpath = Path.Combine(basedir, fileName);
                if (!File.Exists(fullpath)) { files.Add(item); continue; }
                var hash = FileHashHelper.ComputeHash(fullpath);
                if (string.Compare(hash, item.Hash, false) != 0) files.Add(item);
            }
            if (files.Count == 0) { back(); return; }
            var index = 0;
            var count = files.Count;
            Action increase = () =>
            {
                index++;
                if (index == count) back();
            };
            foreach (var file in files)
            {
                var filename = file.Name;
                if (start != null) start(filename);
                DownloadFile(file.Name, e =>
                {
                    if (end != null) end(false, filename);
                    increase();
                }, () =>
                {
                    if (end != null) end(true, filename);
                    increase();
                });
            }
        }

        public static void Start(string entrypoint)
        {
            var fullpath = Path.Combine(basedir, entrypoint);
            Process process = new Process();
            process.StartInfo.FileName = fullpath;
            process.StartInfo.UseShellExecute = false;
            process.Start();
        }
    }
}
